package eventServer;

import java.io.*;
import java.net.*;

import eventServer.NodeEventServer;
import event.Event;
import node.NodeDescriptor;

/**
 * This class represents the worker thread that will run the service and implements 
 * the Runnable interface and EPolicy interface 
 * @see java.lang.Runnable
 * @see eventServer.EPolicy
 * @author 
 *
 */
public class NodeEventServant implements Runnable, EPolicy{
	
	protected DatagramPacket receivedDatagram;
	protected NodeEventServer theServer;
	
	/**
	 * Builder
	 * @param server
	 * @param thePacket
	 */
	public NodeEventServant(NodeEventServer server, DatagramPacket thePacket)
	{
		super();
		receivedDatagram = thePacket;
		theServer = server;
	}
	
	/**
	 * Implementation of the run method of the Runnable interface.
	 * Receive the string containing the demand function from remote node and check if it is available, 
	 * if it is, send an acknowledge with value 0, then the demand service has performed 
	 * if it isn't then send an acknowledge with value -1
	 * 
	 * @see java.io
	 * @see java.net
	 * @exception IOException
	 * @exception SocketException
	 */
	@Override
	public void run() {
		// TODO Auto-generated method stub
		// GET THE PAYLOAD
				byte[] receivedPayload 		= receivedDatagram.getData();
				InetAddress senderAddress 	= receivedDatagram.getAddress();
				int senderPort 				= receivedDatagram.getPort();
				String receivedString		= null;
				
				try {
					ByteArrayInputStream bais 	= new ByteArrayInputStream(receivedPayload);
					ObjectInputStream ois		= new ObjectInputStream(bais);
					receivedString 				= ois.readUTF();
					ois.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					System.out.println(" >> NODE > NodeEventServant: Cannot Parse Received UDP Payload.\n\t" + e.getMessage() );
					return;
				}
				
				
				int ackValue = -1;
				
				// VERIFY IF REQUEST IS VALID
				if(this.verifyEventFunctionAvailability(receivedString))
				{
					System.out.println(" >> NODE > NodeEventServant: Command " + receivedString + " is Valid");
					ackValue = 0;
				}
				else
				{
					System.out.println(" >> NODE > NodeEventServant: Command " + receivedString + " is NOT Valid");
					ackValue = 1;
				}
					
				// SEND AN ACK
				try {
					System.out.println(" >> NODE > NodeEventServant: Sending ACK: " + String.valueOf(ackValue));
					DatagramSocket theSocket = this.sendAckToEventkWithValue(senderAddress, senderPort, ackValue);
					
					// IF REQUEST IS VALID WE HAVE TO DO THE SERVICE.
					if(ackValue==0)
					{
						// DO THE SERVICE
						this.doService(theSocket, receivedString, senderAddress, senderPort);
						
						// CLOSE SOCKET
						theSocket.close();
					}
				} catch (SocketException e) {
					// TODO Auto-generated catch block
					System.out.println(" >> NODE > EventServant: Cannot Creat the Socket. Cannot Send the ACK to " + senderAddress);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					System.out.println(" >> NODE > EventServant: Cannot Read - IO Errors\nDetail: " + e.getMessage());
				}
	}
	
	/**
	 * This function make a datagram socket that contains the response acknowledge about the required event
	 * 
	 * @param destinationAddress
	 * @param destinationPort
	 * @param responseValue
	 * @see java.io
	 * @see java.net
	 * @return A datagram socket containing the response acknowledge	
	 * @throws SocketException
	 * @throws IOException
	 */
	private DatagramSocket sendAckToEventkWithValue(	InetAddress destinationAddress, 
			int destinationPort, 
			int responseValue) 
		throws SocketException, IOException
		{
			//	ResponsePacket
			byte[] responsePayload = new byte[1024];

			// Serialize to a byte array
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream out = new ObjectOutputStream(bos) ;
			out.writeInt(responseValue);
			out.flush();
			out.close();

			responsePayload = bos.toByteArray();

			DatagramPacket responsePacket = new DatagramPacket(responsePayload, responsePayload.length, destinationAddress, destinationPort);

			//	ResponseSocket
			DatagramSocket responseSocket = new DatagramSocket();
			responseSocket.send(responsePacket);
			return responseSocket;		
		}
	

	/**
	 * Scans the receivedString and active the enqueueEventFromRemoteNode or the dequeueEventSendingToRemoteNode method 
	 * 
	 * @param theSocket
	 * @param receivedString
	 * @param destinationAddress
	 * @param destinationPort
	 */
	public void doService(DatagramSocket theSocket, String receivedString, InetAddress destinationAddress, int destinationPort)
	{
		// EES = Enqueue
		if(receivedString.equalsIgnoreCase(NodeDescriptor.getInstance().getEES().getCodice()))
		{
			try {
				this.enqueueEventFromRemoteNode(theSocket);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				System.out.println(" >> NODE > EventServant: EES Command - Error on IO " + e.getMessage());
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				System.out.println(" >> NODE > EventServant: EES Command - Cannot Recognize the Class " + e.getMessage());
			}
		}
		// DES = Dequeue
		else if(receivedString.equalsIgnoreCase(NodeDescriptor.getInstance().getDES().getCodice()))
		{
			try {
				this.dequeueEventSendingToRemoteNode(theSocket, destinationAddress, destinationPort);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				System.out.println(" >> NODE > EventServant: DTS Command - Error on IO " + e.getMessage());
			}
		}
	}
	
	
	/**
	 * Insert an event from remote node into the event queue using the Server instance
	 * 
	 * @param theSocket
	 * @see java.io
	 * @see java.net
	 * @see eventServer.NodeEventServer
	 * @see event.Event
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private void enqueueEventFromRemoteNode(DatagramSocket theSocket) throws IOException, ClassNotFoundException
	{
		// THE SERVANT'LL GET BLOCKED 'TIL A NEW EVENT REQUEST COMES
		byte[] eventBuffer = new byte[65536];
		DatagramPacket eventPacket = new DatagramPacket(eventBuffer, eventBuffer.length);
		theSocket.setSoTimeout(NodeDescriptor.getInstance().getE_timeout());
		theSocket.receive(eventPacket);
		
		// UNMARSHALL THE STREAM TO GET THE STRING REQUEST
		byte[] receivedPayload = eventPacket.getData();
		ByteArrayInputStream bais = new ByteArrayInputStream(receivedPayload);
		ObjectInputStream ios = new ObjectInputStream(bais);
		Event theEvent = (Event)ios.readObject();
		
		// Submit Event inside the Real Queue through Server Instance.
		theServer.submitEvent(theEvent);
	}
	
	
	/**
	 * Take an event from the event queue and send it to remote node with address destinationHost and port destinationPort
	 *  
	 * @param theSocket
	 * @param destinationHost
	 * @see java.io
	 * @see java.net
	 * @see event.Event
	 * @see eventServer.NodeEventServer
	 * @param destinationPort
	 * @throws IOException
	 */
	private void dequeueEventSendingToRemoteNode(DatagramSocket theSocket, InetAddress destinationHost, int destinationPort) throws IOException
	{
		Event currentEvent = theServer.dequeueEvent();
		
		if(currentEvent!=null)
		{
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(currentEvent);
			oos.flush();
			oos.close();
			
			byte[] eventByteArray = baos.toByteArray();
			DatagramPacket eventPacket = new DatagramPacket(eventByteArray, eventByteArray.length, destinationHost, destinationPort);
			
			theSocket.send(eventPacket);
		}
	}
		
	/**
	 * 
	 */
	@Override
	public boolean applyPolicy() {
		// TODO Auto-generated method stub
		return false;
	}
	

	/**
	 * This function check if the event enqueue function or the event dequeue function are availability
	 * 
	 * @return a boolean value representing the availability  of the event enqueue function or of the event dequeue function
	 */
	@Override
	public boolean verifyEventFunctionAvailability(String function) {
		// TODO Auto-generated method stub
		return (function.equalsIgnoreCase(NodeDescriptor.getInstance().getEES().getCodice()) == true ||
				function.equalsIgnoreCase(NodeDescriptor.getInstance().getDES().getCodice()) == true);
	} 
}